﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using Kindohm.Life.Library;

namespace Kindohm.Life.Construct
{

    class Program
    {
        Board board;
        World world;
        Simulation simulation;
        bool running;
        bool paused;
        int heartbeatDuration;
        bool showDeaths;
        bool showAttacks;
        bool showMating;
        bool showEating;
        bool showWalking;
        
        static readonly object runningLock = new object();
        static readonly object pauseLock = new object();

        public bool Running
        {
            get
            {
                lock (runningLock)
                {
                    return this.running;
                }
            }
            set
            {
                lock (runningLock)
                {
                    this.running = value;
                }
            }
        }

        public bool Paused
        {
            get
            {
                lock (pauseLock)
                {
                    return this.paused;
                }
            }
            set
            {
                lock (pauseLock)
                {
                    this.paused = value;
                }
            }
        }

        private Program()
        {
            

            this.showAttacks = false;
            this.showDeaths = true;
            this.showMating = true;
        }

        void Reset(int width, int height, int herbivores, int carnivores, int plants, int heartbeatDuration)
        {

            board = new SimpleBoard();
            simulation = Simulation.Create();
            world = new World(simulation); 
            
            this.heartbeatDuration = heartbeatDuration;
            board.Width = width;
            board.Height = height;
            board.Initialize();

            if (herbivores > 0)
            {
                this.AddObjects(typeof(Herbivore), herbivores);
            }

            if (carnivores > 0)
            {
                this.AddObjects(typeof(Carnivore), carnivores);
            }

            if (plants > 0)
            {
                this.AddObjects(typeof(Plant), plants);
            }

        }

        void AddObjects(Type type, int count) 
        {
            int nextX, nextY;

            for (int i = 0; i < count; i++)
            {
                nextX = Randomizer.Next(0, this.board.Width);
                nextY = Randomizer.Next(0, this.board.Height);
                Point p = new Point(nextX, nextY);
                BoardObject item = this.board.BoardObjects[p];

                while (item != null)
                {
                    nextX = Randomizer.Next(0, this.board.Width);
                    nextY = Randomizer.Next(0, this.board.Height);
                    p = new Point(nextX, nextY);
                    item = this.board.BoardObjects[p];
                }

                //object newItem = type.InvokeMember("Create", System.Reflection.BindingFlags.Static, null, null, null);
                System.Reflection.MethodInfo m = type.GetMethod("Create");
                object newItem = m.Invoke(null, null);

                HeartbeatObject newHeartbeatObject = newItem as HeartbeatObject;

                world.RegisterNewObject(newHeartbeatObject);
                board.AddObject(newHeartbeatObject, p);

                if (newItem is Creature)
                {
                    this.RegisterCreature(newItem as Creature);
                }
            }
        }

        static void Main(string[] args)
        {
            try
            {
                Program newProgram = new Program();
                ThreadStart start = new ThreadStart(newProgram.Run);
                Thread newThread = new Thread(start);
                newProgram.Running = true;
                newProgram.Paused = true;
                newProgram.DoHelp();

                if (args != null && args.Length > 0)
                {
                    if (args[0] == "auto")
                    {
                        newProgram.Reset(300, 300, 300, 50, 200, 1);
                        newProgram.paused = false;
                    }
                }


                newThread.Start();

                Console.WriteLine("Type 'new' to start a new environment.");
                Console.WriteLine();

                string input = Console.ReadLine();
                while (input != "exit")
                {

                    if (input == "stop" || input == "pause")
                    {
                        newProgram.Paused = true;
                    }

                    if (input == "go")
                    {
                        newProgram.Paused = false;
                    }

                    if (input.StartsWith("pace "))
                    {
                        newProgram.DoPace(input);
                    }

                    if (input == "list")
                    {
                        newProgram.DoList();
                    }

                    if (input == "world")
                    {
                        newProgram.DoWorld();
                    }

                    if (input.StartsWith("show"))
                    {
                        newProgram.DoShow(input);
                    }

                    if (input.StartsWith("get"))
                    {
                        newProgram.DoGet(input);
                    }

                    if (input.StartsWith("hide"))
                    {
                        newProgram.DoHide(input);
                    }

                    if (input == "new")
                    {
                        newProgram.Paused = true;
                        newProgram.DoNew();
                    }

                    if (input == "help")
                    {
                        newProgram.DoHelp();
                    }
                    
                    Console.WriteLine();
                    input = Console.ReadLine();
                }

                newProgram.Paused = false;
                newProgram.Running = false;
                newThread.Join();

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public void DoShow(string input)
        {
            //"show "
            string value = input.Substring(5);

            if (string.Compare(value, "eating", true) == 0)
            {
                this.showEating = true;
                Console.WriteLine("Eating shown.");
            }

            if (string.Compare(value, "deaths", true) == 0)
            {
                this.showDeaths = true;
                Console.WriteLine("Deaths shown.");
            }
            if (string.Compare(value, "attacks", true) == 0)
            {
                this.showAttacks = true;
                Console.WriteLine("Attacks shown.");
            }
            if (string.Compare(value, "mating", true) == 0)
            {
                this.showMating = true;
                Console.WriteLine("Mating shown.");
            }
            if (string.Compare(value, "walking", true) == 0)
            {
                this.showWalking = true;
                Console.WriteLine("Walking shown.");
            }
        }

        public void DoWorld()
        {
            WorldInfo info = this.world.GetWorldInfo();
            Console.WriteLine("Creatures: {0}", info.Creatures.ToString());
            Console.WriteLine("Herbivores: {0}", info.Herbivores.ToString());
            Console.WriteLine("Carnivores: {0}", info.Carnivores.ToString());
            Console.WriteLine("Plants: {0}", info.Plants.ToString());
            Console.WriteLine("Last cycle duration: {0}", this.world.LastCycleDuration.ToString());
            Console.WriteLine("Ticks: {0}", this.world.Ticks.ToString());
            Console.WriteLine();
        }

        public void DoHide(string input)
        {
            //"hide "
            string value = input.Substring(5);

            if (string.Compare(value, "eating", true) == 0)
            {
                this.showEating = false;
                Console.WriteLine("Eating hidden.");
            }
            if (string.Compare(value, "deaths", true) == 0)
            {
                this.showDeaths = false;
                Console.WriteLine("Deaths hidden.");
            }
            if (string.Compare(value, "attacks", true) == 0)
            {
                this.showAttacks = false;
                Console.WriteLine("Attacks hidden.");
            }
            if (string.Compare(value, "mating", true) == 0)
            {
                this.showMating = false;
                Console.WriteLine("Mating hidden.");
            }
            if (string.Compare(value, "walking", true) == 0)
            {
                this.showWalking = false;
                Console.WriteLine("Walking hidden.");
            }
        }

        public void DoPace(string command)
        {
            //"pace "
            string value = command.Substring(5);
            int pace = int.Parse(value);
            this.heartbeatDuration = pace;
            Console.WriteLine("Heartbeat duration set to {0}.", pace.ToString());
        }

        void DoGet(string input)
        {
            //"get "
            string value = input.Substring(4);
            Collection<Creature> items = this.world.FindObjectsByName(value);

            if (items.Count == 0)
            {
                Console.WriteLine("No items were found with that name.");
            }
            else
            {
                foreach (Creature item in items)
                {
                    this.ShowCreature(item, false);
                }
            }
        }

        void DoList()
        {
            int count = 0;
            foreach (HeartbeatObject item in this.world.HeartbeatObjects)
            {
                if (item is Creature)
                {
                    count = count + 1;
                    this.ShowCreature(item as Creature, true);
                }
            }
            Console.WriteLine("{0} creatures found.", count.ToString());
        }

        void DoNew()
        {
            Console.Write("Board width: ");
            int width = int.Parse(Console.ReadLine());
            Console.Write("Board height: ");
            int height = int.Parse(Console.ReadLine());
            Console.Write("# of herbivores: ");
            int herbivores = int.Parse(Console.ReadLine());
            Console.Write("# of carnivores: ");
            int carnivores = int.Parse(Console.ReadLine());
            Console.Write("# of plants: ");
            int plants = int.Parse(Console.ReadLine());

            this.Reset(width, height, herbivores, carnivores, plants, this.heartbeatDuration);

            Console.WriteLine();
            Console.WriteLine("Environment has been reset.");
            Console.WriteLine("Environment paused: " + this.Paused.ToString());
            Console.WriteLine("Type \"go\" to start the environment if it is not running.");
            Console.WriteLine();

        }

        void ShowCreature(Creature c)
        {
            this.ShowCreature(c, false);
        }

        void ShowCreature(Creature c, bool brief)
        {
            string type = "Creature";
            if (c is Herbivore)
            {
                type = "Herbivore";
            }
            else if (c is Carnivore)
            {
                type = "Carnivore";
            }

            if (!brief)
            {

                Console.WriteLine("Name: {0}", c.Name);

                Console.Write("\tClass: {0}", type);
                Console.WriteLine("\tGeneration: {0}", c.Generation.ToString());
                Console.WriteLine("\tPosition: {0}\tSex: {1}", c.Position.ToString(), c.Sex.ToString());
                Console.Write("\tHealth: {0}/{1}", c.Health.ToString(), c.MaxHealth.ToString());
                Console.WriteLine("\t\tHunger: {0}/{1}/{2}", c.Hunger.ToString(), c.HungerHuntThreshold.ToString(), c.HungerStarvationThreshold.ToString());
                Console.Write("\tSight: {0}", c.Sight.ToString());
                Console.WriteLine("\t\tHeartbeats: {0}/{1}", c.Heartbeats.ToString(), c.MaxHeartbeats.ToString());
                Console.Write("\tLibido: {0}/{1}", c.Libido.ToString(), c.MatingThreshold.ToString());
                Console.WriteLine("\t\tChildren: {0}", c.Children.ToString());

                if (c is Carnivore)
                {
                    Carnivore carnivore = c as Carnivore;
                    Console.WriteLine("\tKills: {0}", carnivore.Kills.ToString());
                }
                Console.WriteLine();
            }
            else
            {

                Console.WriteLine("{0}", c.Name);
                Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{9}/{10}\t{4}/{5}/{6}\t{7}/{8}", string.Empty, c.Position.ToString(), type, c.Sex.ToString(),
                    c.Hunger.ToString(), c.HungerHuntThreshold.ToString(), c.HungerStarvationThreshold.ToString(),
                    c.Libido.ToString(), c.MatingThreshold.ToString(),
                    c.Health.ToString(), c.MaxHealth.ToString());
                Console.WriteLine();
            }

        }

        public void DoHelp()
        {
            Console.WriteLine();
            Console.WriteLine("**** Kindohm.Life Help ****");
            Console.WriteLine();
            Console.WriteLine("Commands:");
            Console.WriteLine("help\t\t\t: Displays this screen.");
            Console.WriteLine();
            Console.WriteLine("go\t\t\t: Start simulator.");
            Console.WriteLine();
            Console.WriteLine("stop\t\t\t: Pause simulator.");
            Console.WriteLine();
            Console.WriteLine("pace [n]\t\t: Sets heartbeat interval, where n = interval in\n\t\t\t  milleseconds.");
            Console.WriteLine();
            Console.WriteLine("show|hide [event]\t: Shows or hides events in the system.  Valid values \n" +
                "\t\t\t  for [event] include: attacks|eating|mating|deaths|walking.");
            Console.WriteLine();
            Console.WriteLine("list\t\t\t: Lists all creatures in the environment.");
            Console.WriteLine();
            Console.WriteLine("world\t\t\t: Shows general information about the environment.");
            Console.WriteLine();
            Console.WriteLine("get [n]\t\t\t: Get info about a specific creature, where n = \n\t\t\t" +
                "  creature name (case insensitive).  Just type the first few \n\t\t\t" +
                "  chars to get a match. (e.g. \"mat\" will match \"Matthew\".");
            Console.WriteLine();
            Console.WriteLine("**** End Help ****");
            Console.WriteLine();
        }

        void RegisterCreature(Creature creature)
        {
            creature.Mating += new EventHandler<MateEventArgs>(creature_Mating);
            creature.Died += new EventHandler<DiedEventArgs>(creature_Died);
            creature.Eating += new EventHandler<EatingEventArgs>(creature_Eating);
            creature.Walking += new EventHandler<EventArgs>(creature_Walking);
            creature.ProducedRemains += new EventHandler<ProducedRemainsEventArgs>(creature_ProducedRemains);
            creature.Mutating += new EventHandler<MutatingEventArgs>(creature_Mutating);
            if (creature is Carnivore)
            {
                Carnivore carnivore = creature as Carnivore;
                carnivore.Attacking += new EventHandler<AttackEventArgs>(carnivore_Attacking);
            }
        }

        void creature_Mutating(object sender, MutatingEventArgs e)
        {
            Console.WriteLine();
            Console.WriteLine("MUTATE!");
            Creature c = sender as Creature;
            Console.WriteLine("{0} mutated its {1} by a factor of {2} from {4} to {3}.", c.Name, e.Property, e.Factor.ToString(), e.NewValue.ToString(), e.OldValue.ToString());
            Console.WriteLine();
        }

        void creature_ProducedRemains(object sender, ProducedRemainsEventArgs e)
        {
            Corpse corpse = e.Remains as Corpse;
            if (corpse != null)
            {
                corpse.ProducedPlant += new EventHandler<EventArgs>(corpse_ProducedPlant);
            }
        }

        void corpse_ProducedPlant(object sender, EventArgs e)
        {
            Console.WriteLine("Corpse produced a plant.");
        }

        void creature_Walking(object sender, EventArgs e)
        {
            if (this.showWalking)
            {
                Creature creature = sender as Creature;
                Console.WriteLine("{0} moved to {1}.", creature.Name, creature.Position.ToString());
            }
        }

        void creature_Eating(object sender, EatingEventArgs e)
        {
            if (this.showEating)
            {
                Creature creature = sender as Creature;
                string foodType = e.Food.GetType().Name;
                Console.WriteLine("{0} ate a {1}.\tNutrition: {7}\tHealth: {2}/{3}\tHunger: {4}/{5}/{6}", creature.Name, foodType,
                    creature.Health.ToString(), creature.MaxHealth.ToString(),
                    creature.Hunger.ToString(), creature.HungerHuntThreshold.ToString(),
                    creature.HungerStarvationThreshold.ToString(), e.Nutrition.ToString());
            }
        }

        void carnivore_Attacking(object sender, AttackEventArgs e)
        {
            if (this.showAttacks)
            {
                Console.WriteLine("{0} attacked {1}.", e.Attacker.Name, e.Prey.Name);
            }
        }


        void creature_Died(object sender, DiedEventArgs e)
        {
            if (this.showDeaths)
            {
                TerminalLifeForm item = sender as TerminalLifeForm;
                Console.WriteLine("{0} died [{1}].", item.Name, item.CauseOfDeath.ToString());
            }
        }

        void creature_Mating(object sender, MateEventArgs e)
        {
            if (this.showMating)
            {
                Console.WriteLine("{0} mated with {1} and produced {2}.", e.Creature.Name, e.Mate.Name, e.Offspring.Name);
                this.ShowCreature(e.Creature);
                Console.WriteLine();
                this.ShowCreature(e.Mate);
                Console.WriteLine();
                this.ShowCreature(e.Offspring);
                Console.WriteLine();
            }

            this.RegisterCreature(e.Offspring);

        }              

        public void Run()
        {
            bool firstPause = false;
            while (this.Running)
            {
                while (this.Paused)
                {
                    if (!firstPause)
                    {
                        Console.WriteLine("Paused");
                        Console.WriteLine();
                        firstPause = true;
                    }
                    Thread.Sleep(100);
                }
                if (firstPause)
                {
                    Console.WriteLine("Resuming.");
                    Console.WriteLine();
                }
                firstPause = false;
                world.Tick();
                Thread.Sleep(this.heartbeatDuration);
            }
        }

       
    }
}
